﻿using Microsoft.AspNetCore.Http;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Infrastructure;
using PMS.Core.Utils;
using PMS.Data.DbUtils;
using PMS.Data.Entities.Medias;
using PMS.Data.Entities.Settings;
using PMS.Services.EntityServices.Logging;
using PMS.Services.Utils;
using System;
using System.IO;

namespace PMS.Services.EntityServices.Medias
{
    /// <summary>
    /// MediaFileService as Default with medias stored in local server
    /// </summary>
    public class MediaFileLocalService : BaseMediaFileService
    {
        #region Fileds
        private readonly ILogger _logger;
        private readonly IPMSFileProvider _fileProvider;
        private readonly IEventDispatcher _eventDispatcher;
        private readonly IRepository<MediaFile> _mediaFileRepository;
        #endregion

        #region Ctor

        public MediaFileLocalService(MediaSettings mediaSettings,
            ILogger logger,
            IEventDispatcher eventDispatcher,
            IHttpContextAccessor httpContextAccessor,
            IRepository<MediaFile> mediaFileRepository)
             : base(mediaSettings,
                  eventDispatcher,
                  httpContextAccessor,
                  mediaFileRepository)
        {
            _logger = logger;
            _fileProvider = CommonHelper.DefaultFileProvider;
            _eventDispatcher = eventDispatcher;
            _mediaFileRepository = mediaFileRepository;
        }
        #endregion

        #region Utils
        /// <summary>
        /// Get mediafile local path. Used when file stored on file system (not in the database)
        /// </summary>
        /// <param name="fileExtName">File Extension Name without '.'</param>
        /// <returns>Local picture path</returns>
        protected virtual string GetMediaFileDirectoryInLocalPath(string fileExtName)
        {
            //if (string.IsNullOrEmpty(fileName)) fileName = GenerateUniqueMediaFileName();
            var category = MimeTypesManager.Instance.GetFileCategoryFromExtName(fileExtName);
            if (category == MediaFileType.Unknown)
                throw new PMSException("Un Support File with file extension name:" + fileExtName);
            var BaseUploadedLocalPath = _fileProvider.GetAbsolutePath(MediaDefaultConfigs.MediaFileDirectoryPath);
            if (_fileProvider.DirectoryExists(BaseUploadedLocalPath))
            {
                _fileProvider.CreateDirectory(BaseUploadedLocalPath);
            }
            string fileDir  = _fileProvider.GetAbsolutePath(MediaDefaultConfigs.MediaFileDirectoryPath,
                Enum.GetName(category.GetType(), category),
                DateTime.Now.ToString("yyyyMMdd"));
            if(!_fileProvider.DirectoryExists(fileDir)) _fileProvider.CreateDirectory(fileDir);
            return fileDir;
        }

        /// <summary>
        /// Get MediaFile local path. Used when images stored on file system (not in the database)
        /// </summary>
        /// <param name="file">MediaFile</param>
        /// <param name="getOrsave">
        /// true: for get image path from local, if picture not exsits returns ""
        /// false:for save image on local, return local path for image Overwrite
        /// </param>
        /// <returns>Local picture path if file exsits</returns>
        protected virtual string GetMediaFileLocalPath(MediaFile file, bool getOrsave = true)
        {
            var fileName = $"{file.SeoFilename}.{file.ExtensionName}";
            var filePath = _fileProvider.GetAbsolutePath($"{file.VirtualPath.TrimStart('~')}", fileName);
            if (!_fileProvider.FileExists(filePath) && getOrsave) return "";
            return filePath;
        }

        /// <summary>
        /// Save mediafile on file system
        /// </summary>
        /// <param name="file">FileUpload record</param>
        /// <param name="fileBinary">file binary</param>
        protected virtual void SaveFileInLocal(MediaFile file, byte[] fileBinary)
        {
            var filePath = GetMediaFileLocalPath(file, false);
            if (fileBinary != null)
                _fileProvider.WriteAllBytes(filePath, fileBinary);
        }
        /// <summary>
        /// Loads a picture from file Only For ImageFile
        /// </summary>
        /// <param name="file">MediaFile entity</param>
        /// <returns>Picture binary</returns>
        protected virtual byte[] LoadPictureFromFile(MediaFile file)
        {
            var category = MimeTypesManager.Instance.GetFileCategoryFromExtName(file.ExtensionName);
            if (category != MediaFileType.Image) throw new PMSException("Only For ImageFile Use", SystemLogLevel.Error);
            var filePath = GetMediaFileLocalPath(file);
            if (string.IsNullOrEmpty(filePath)) throw new PMSException($"Image Not Exsits In {filePath}", SystemLogLevel.Error);
            return _fileProvider.ReadAllBytes(filePath);
        }

        /// <summary>
        /// Delete a MediaFile on file system
        /// </summary>
        /// <param name="picture">MediaFile</param>
        protected virtual void DeleteMediaFileOnFileSystem(MediaFile file)
        {
            if (file == null)
                throw new ArgumentNullException(nameof(file));

            var fileName = $"{file.SeoFilename}.{file.ExtensionName}";
            var filePath = _fileProvider.GetAbsolutePath($"{file.VirtualPath.TrimStart('~')}", fileName);
            if(_fileProvider.FileExists(filePath))
                _fileProvider.DeleteFile(filePath);
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the loaded picture binary only when MediaFile is Image
        /// depending on media storage settings
        /// </summary>
        /// <param name="imageFile">MediaFile</param>
        /// <returns>Picture binary</returns>
        public override byte[] LoadPictureBinary(MediaFile imageFile)
        {
            if (imageFile == null)
                throw new ArgumentNullException(nameof(imageFile));

            if (imageFile.StoreMode != MediaFileStoreMode.LocalFile)
                throw new PMSException("LocalStorage Image Only", SystemLogLevel.Error);
            try
            {
                return LoadPictureFromFile(imageFile);
            }
            catch (Exception e)
            {
                if (e is PMSException)
                {
                    Console.WriteLine(e);
                    return null;
                }
                throw e;
            }
        }

        /// <summary>
        /// Deletes a MediaEntity
        /// </summary>
        /// <param name="file">MediaFile</param>
        public override bool DeleteMediaFile(MediaFile file)
        {
            if (file == null)
                throw new ArgumentNullException(nameof(file));

            try
            {
                //delete thumbs if file is Picture
                DeletePictureThumbs(file);

                //delete from file system
                DeleteMediaFileOnFileSystem(file);
            }
            catch (DirectoryNotFoundException)
            {
                //DoNothing
            }
            catch (UnauthorizedAccessException uaex)
            {
                _logger.Error("DeleteMediaFile Local Error", uaex);
            }

            //delete from database
            _mediaFileRepository.Delete(file);
            //event notification
            _eventDispatcher.EntityDeleted(file);
            return true;
        }

        /// <summary>
        /// same as (bool IsDefault, string FileUrl) GetMediaFileUrl(int fileId, int targetSize = 0); 
        /// </summary>
        public override (bool IsDefault, string FileUrl, string thumbFileUrl) GetMediaFileUrl(ref MediaFile mediaFile, int targetSize = 0)
        {
            if (mediaFile == null)
                //throw new ArgumentNullException("mediaFile");
                return (true, GetDefaultPictureUrl(), GetDefaultPictureUrl(targetSize));

            var fileCategory = MimeTypesManager.Instance.GetFileCategoryFromExtName(mediaFile.ExtensionName);
            if (fileCategory == MediaFileType.Unknown)
                throw new PMSException($"UnSupportMediaFile Stored with MediaFile id: {mediaFile.Id}", SystemLogLevel.Error);
            
            var fileLocalPath = GetMediaFileLocalPath(mediaFile);
            var fileName = $"{mediaFile.SeoFilename}.{mediaFile.ExtensionName}";
            if (fileCategory == MediaFileType.Image && string.IsNullOrEmpty(fileLocalPath))
                return (true, GetDefaultPictureUrl(), GetDefaultPictureUrl(targetSize));
            else if(fileCategory == MediaFileType.Image && targetSize > 0)
            {
                var thumbFileName = $"{mediaFile.Id:0000000}_{targetSize}.{mediaFile.ExtensionName}";
                var thumbFilePath = GetThumbLocalPath(thumbFileName);

                if (!_fileProvider.FileExists(thumbFilePath))
                {
                    var pictureBinary = ResizePicture(_fileProvider.ReadAllBytes(thumbFilePath), true, targetSize);
                    SaveThumb(thumbFilePath, pictureBinary);
                }

                var url = GetMediaFilePathUrl(_fileProvider.GetVirtualPath(thumbFilePath).TrimStart('~')) + thumbFileName;
                return (false, GetMediaFilePathUrl(mediaFile.VirtualPath.TrimStart('~')), url);
            }
            
            return (false, GetMediaFilePathUrl(mediaFile.VirtualPath.TrimStart('~')) + fileName, "");
        }
        /// <summary>
        /// Shortcut for insert a media file as Image
        /// Inserts a picture from pictureBinary, For scenario like base64 image
        /// </summary>
        /// <param name="pictureBinary">The picture binary</param>
        /// <param name="mimeType">The picture MIME type</param>
        /// <param name="useage">The useage of the image</param>
        /// <param name="title">"title" attribute for Image</param>
        /// <param name="description">"description" attribute for Image</param>
        /// <param name="imagelink">imagelink for image "href"</param>
        /// <returns>MediaFile</returns>
        public override MediaFile InsertPicture(byte[] pictureBinary, string mimeType, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            mimeType = CommonHelper.EnsureNotNull(mimeType);
            mimeType = CommonHelper.EnsureMaximumLength(mimeType, 20);

            if (MimeTypesManager.Instance.GetFileCategoryByMimeType(mimeType) != MediaFileType.Image)
                return null;
            //Resize Image when Input Image larger than _mediaSetting.MaximumImageWidth
            var resizedImgBinary = ResizePicture(pictureBinary);
            resizedImgBinary = CompressPicture(resizedImgBinary);
            var picture = new MediaFile
            {
                SeoFilename = GenerateUniqueMediaFileName(),
                ExtensionName = GetOutputFileExtensionFromMimeType(mimeType),
                Useage = useage,
                Title = title,
                Description = description,
                ImageLink = imagelink,
                StoreMode = MediaFileStoreMode.LocalFile,
                CreatedOnTimeUtc = DateTime.UtcNow,
                UpdatedOnTimeUtc = DateTime.UtcNow
            };
            if (string.IsNullOrEmpty(picture.ExtensionName))
                throw new PMSException("Unsupport Image Extension insert", SystemLogLevel.Error);
            else picture.ExtensionName = picture.ExtensionName.ToLowerInvariant();
            picture.VirtualPath = _fileProvider.GetVirtualPath(GetMediaFileDirectoryInLocalPath(picture.ExtensionName));
            _mediaFileRepository.Insert(picture);

            SaveFileInLocal(picture, resizedImgBinary);
            //event notification
            _eventDispatcher.EntityInserted(picture);
            return picture;
        }

        /// <summary>
        /// Shortcut for Updates a media file as Image
        /// Update a picture from pictureBinary, For scenario like base64 image
        /// </summary>
        /// <param name="pictureId">The picture identifier to be upload</param>
        /// <param name="mimeType">The input picture MIME type</param>
        /// <param name="pictureBinary">The input picture binary</param>
        /// <param name="useage">The useage of the image</param>
        /// <param name="title">"title" attribute for Image</param>
        /// <param name="description">"description" attribute for Image</param>
        /// <param name="imagelink">imagelink for image "href"</param>
        /// <returns>Picture</returns>
        public override MediaFile UpdatePicture(int pictureId, string mimeType, byte[] pictureBinary = null, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            if(MimeTypesManager.Instance.GetFileCategoryByMimeType(mimeType) != MediaFileType.Image)
                throw new PMSException("UpdatePicture Method is a Shortcut for Updates a media file as Image, try UpdateMediaFile Instead.", SystemLogLevel.Warning);

            MediaFile targetPicture = GetMediaFileById(pictureId);
            if (targetPicture == null) 
                return null;

            string outputExt = GetOutputFileExtensionFromMimeType(mimeType);
            if (pictureBinary != null)
            {
                try
                {
                    //DeletePictureThumbs if exsits
                    DeletePictureThumbs(targetPicture);
                    DeleteMediaFileOnFileSystem(targetPicture);
                }
                catch (DirectoryNotFoundException)
                {
                    //DoNothing
                }
                catch (UnauthorizedAccessException ex)
                {
                    _logger.Error("ImageFile Delete FromLocal Error, Delete them by yourself", ex);
                }
                
                //Resize Image when Input Image larger than _mediaSetting.MaximumImageWidth
                var resizedImgBinary = ResizePicture(pictureBinary);
                resizedImgBinary = CompressPicture(resizedImgBinary);
                targetPicture.VirtualPath = _fileProvider.GetVirtualPath(GetMediaFileDirectoryInLocalPath(outputExt));
                targetPicture.SeoFilename = GenerateUniqueMediaFileName();
                targetPicture.ExtensionName = outputExt;
                SaveFileInLocal(targetPicture, resizedImgBinary);
            }
            targetPicture.Id = pictureId;
            targetPicture.Useage = useage;
            targetPicture.Title = title;
            targetPicture.Description = description;
            targetPicture.ImageLink = imagelink;
            targetPicture.StoreMode = MediaFileStoreMode.LocalFile;
            return UpdateMediaFileInfoOnly(targetPicture);
        }

        /// <summary>
        /// Inserts a MediaFile
        /// </summary>
        /// <param name="formFile">Form file</param>
        /// <param name="useage">The useage of the MediaFile</param>
        /// <param name="title">"title" attribute for MediaFile</param>
        /// <param name="description">"description" attribute for MediaFile</param>
        /// <param name="imagelink">imagelink for image "href" if its a Image</param>
        /// <returns>MediaFile</returns>
        public override MediaFile InsertMediaFile(IFormFile formFile, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            var fileName = formFile.FileName;
            string contentType = string.Empty;
            //remove path (passed in IE)
            fileName = _fileProvider.GetFileName(fileName);
            string fileExtension;
            if (!string.IsNullOrEmpty(fileName))
                fileExtension = GetOutputFileExtensionFromFileName(fileName);
            else
            {
                try
                {
                    contentType = formFile.ContentType;
                    fileExtension = GetOutputFileExtensionFromMimeType(contentType);
                }
                catch(ArgumentNullException)
                {
                    throw new PMSException("Cannot auto detect file extension while both fileName and contenType are Empty.", SystemLogLevel.Error);
                }
            }

            if (!string.IsNullOrEmpty(fileExtension))
                fileExtension = fileExtension.ToLowerInvariant();
            else throw new PMSException("MediaFile ExtensionName cannot be Empty", SystemLogLevel.Error);

            //Use ShortCut Insert for ImageFile Insert for further resize/compress 
            if(MimeTypesManager.Instance.GetFileCategoryFromExtName(fileExtension) == MediaFileType.Image)
            {
                contentType = string.IsNullOrEmpty(formFile.ContentType)
                            ? MimeTypesManager.Instance.GetFileMimeTypeFromExtName(fileExtension)
                            : formFile.ContentType;
                return InsertPicture(GetMediaFileBits(formFile), contentType, useage, title, description, imagelink);
            }

            var file = new MediaFile
            {
                SeoFilename = GenerateUniqueMediaFileName(),
                ExtensionName = fileExtension,
                Useage = useage,
                Title = title,
                Description = description,
                ImageLink = imagelink,
                StoreMode = MediaFileStoreMode.LocalFile,
                CreatedOnTimeUtc = DateTime.UtcNow,
                UpdatedOnTimeUtc = DateTime.UtcNow
            };
            fileName = $"{file.SeoFilename}.{file.ExtensionName}";
            string filePath = GetMediaFileDirectoryInLocalPath(fileExtension);
            file.VirtualPath = _fileProvider.GetVirtualPath(filePath);
            _mediaFileRepository.Insert(file);

            filePath = _fileProvider.GetAbsolutePath(filePath, fileName);
            using (Stream fileStream = new FileStream(filePath, FileMode.Create))
            {
                formFile.CopyToAsync(fileStream).GetAwaiter();
            }
            //event notification
            _eventDispatcher.EntityInserted(file);

            return file;
        }

        /// <summary>
        /// Update a MediaFile
        /// </summary>
        /// <param name="fileId">The mediafile identifier</param>
        /// <param name="formFile">Form file</param>
        /// <param name="useage">The useage of the MediaFile</param>
        /// <param name="title">"title" attribute for MediaFile</param>
        /// <param name="description">"description" attribute for MediaFile</param>
        /// <param name="imagelink">imagelink for image "href" if its a Image</param>
        /// <returns>MediaFile</returns>
        public override MediaFile UpdateMediaFile(int fileId, IFormFile formFile, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            var targetFile = GetMediaFileById(fileId);
            if (targetFile == null) return null;
            var contentType = string.Empty;
            var fileName = formFile.FileName;
            //remove path (passed in IE)
            fileName = _fileProvider.GetFileName(fileName);
            string fileExtension;
            if (!string.IsNullOrEmpty(fileName))
                fileExtension = GetOutputFileExtensionFromFileName(fileName);
            else
            {
                try
                {
                    contentType = formFile.ContentType;
                    fileExtension = GetOutputFileExtensionFromMimeType(contentType);
                }
                catch (ArgumentNullException)
                {
                    throw new PMSException("Cannot auto detect file extension while both fileName and contenType are Empty.", SystemLogLevel.Error);
                }
            }

            if (!string.IsNullOrEmpty(fileExtension))
                fileExtension = fileExtension.ToLowerInvariant();
            else throw new PMSException("MediaFile ExtensionName cannot be Empty", SystemLogLevel.Error);

            //Use ShortCut Image Update for ImageFile Update for further resize/compress 
            if (MimeTypesManager.Instance.GetFileCategoryFromExtName(fileExtension) == MediaFileType.Image)
            {
                contentType = string.IsNullOrEmpty(formFile.ContentType)
                            ? MimeTypesManager.Instance.GetFileMimeTypeFromExtName(fileExtension)
                            : formFile.ContentType;
                return UpdatePicture(fileId, contentType, GetMediaFileBits(formFile), useage, title, description, imagelink);
            }
                
            try
            {
                DeleteMediaFileOnFileSystem(targetFile);
            }
            catch (UnauthorizedAccessException ex)
            {
                _logger.Error("MediaFile Delete Error, Delete them by yourself", ex);
            }

            targetFile.SeoFilename = GenerateUniqueMediaFileName();
            targetFile.ExtensionName = fileExtension;
            targetFile.Useage = useage;
            targetFile.Title = title;
            targetFile.Description = description;
            targetFile.ImageLink = imagelink;
            targetFile.StoreMode = MediaFileStoreMode.LocalFile;
                        
            fileName = $"{targetFile.SeoFilename}.{targetFile.ExtensionName}";
            string filePath = GetMediaFileDirectoryInLocalPath(fileExtension);
            targetFile.VirtualPath = _fileProvider.GetVirtualPath(filePath);

            filePath = _fileProvider.GetAbsolutePath(filePath, fileName);
            using (Stream fileStream = new FileStream(filePath, FileMode.Create))
            {
                formFile.CopyToAsync(fileStream).GetAwaiter();
            }

            return UpdateMediaFileInfoOnly(targetFile);
        }

        #endregion
    }
}
